﻿using System.Security.Claims;
using Devonline.Communication.Messages;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity;

/// <summary>
/// 认证类扩展方法
/// </summary>
public static class IdentityExtensions
{
    /// <summary>
    /// 针对字符串类型主键的 IIdentity 实例获取对应的 OwnerType
    /// </summary>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType(this IIdentity<string> identity) => identity.GetIdentityType<string>();
    /// <summary>
    /// 针对 IIdentity<TKey> 实例获取对应的 OwnerType
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TKey>(this IIdentity<TKey> identity) where TKey : IConvertible, IEquatable<TKey> => identity.GetIdentityType<IIdentity<TKey>, TKey>();
    /// <summary>
    /// 针对任意引用类型获取对应的身份 OwnerType
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TIdentity, TKey>(this TIdentity identity) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey> => identity switch
    {
        User<TKey> => IdentityType.User,
        Role<TKey> => IdentityType.Role,
        Group<TKey> => IdentityType.Group,
        Level<TKey> => IdentityType.Level,
        _ => IdentityType.System
    };

    /// <summary>
    /// 根据身份选择过滤身份结果
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identities"></param>
    /// <param name="identityTypes"></param>
    /// <returns></returns>
    public static IEnumerable<TIdentity> GetByIdentityType<TIdentity>(this IEnumerable<TIdentity> identities, Dictionary<IdentityType, string[]>? identityTypes = default) where TIdentity : class, IIdentity<string> => identities.GetByIdentityType<TIdentity, string>(identityTypes);
    /// <summary>
    /// 根据身份选择过滤身份结果
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identities"></param>
    /// <param name="identityTypes"></param>
    /// <returns></returns>
    public static IEnumerable<TIdentity> GetByIdentityType<TIdentity, TKey>(this IEnumerable<TIdentity> identities, Dictionary<IdentityType, string[]>? identityTypes = default) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey>
    {
        if (identities.Any() && identityTypes is not null)
        {
            var identityType = identities.First().GetIdentityType<TIdentity, TKey>();
            if (identityTypes.ContainsKey(identityType))
            {
                var results = identityTypes.FirstOrDefault(a => a.Key == identityType).Value;
                if (results is not null && results.Length > 0)
                {
                    identities = identities.Where(x => results.Contains(x.Name));
                }
            }
        }

        return identities;
    }

    /// <summary>
    /// 强制推送客户端更新
    /// </summary>
    /// <returns></returns>
    public static async Task NotifyUserInfoRefreshAsync(this IMessageCommunicator communicator, string userName) => await communicator.RefreshCacheAsync(CACHE_DATA, userName);

    /// <summary>
    /// 获取身份相关调用错误提示
    /// </summary>
    /// <param name="errors"></param>
    /// <returns></returns>
    public static string GetErrorMessage(this IdentityResult identityResult) => (!identityResult.Succeeded && identityResult.Errors.Any()) ? identityResult.Errors.Select(x => $"{x.Code}: {x.Description}").ToString<string>() : identityResult.ToString();

    /// <summary>
    /// 用户上下文脱敏
    /// </summary>
    /// <param name="userContext"></param>
    /// <returns></returns>
    public static UserContext Desensitize(this UserContext userContext)
    {
        userContext.User.Desensitize();
        return userContext;
    }
    /// <summary>
    /// 用户信息脱敏
    /// </summary>
    /// <param name="userViewModel"></param>
    /// <returns></returns>
    public static UserViewModel Desensitize(this UserViewModel userViewModel)
    {
        userViewModel.PhoneNumber = userViewModel.PhoneNumber.Desensitize();
        userViewModel.Email = userViewModel.Email.Desensitize();
        return userViewModel;
    }
    /// <summary>
    /// 获取脱敏后的认证用户信息
    /// </summary>
    /// <param name="authUser"></param>
    /// <returns></returns>
    public static RealNameInfo Desensitize(this RealNameInfo authUser)
    {
        authUser.PhoneNumber = authUser.PhoneNumber.Desensitize();
        authUser.IdCode = authUser.IdCode.Desensitize();
        return authUser;
    }
    /// <summary>
    /// 获取脱敏后的认证用户信息视图模型
    /// </summary>
    /// <param name="authUser"></param>
    /// <returns></returns>
    public static RealNameViewModel Desensitize(this RealNameViewModel authUser)
    {
        authUser.PhoneNumber = authUser.PhoneNumber.Desensitize();
        authUser.IdCode = authUser.IdCode.Desensitize();
        return authUser;
    }

    /// <summary>
    /// 获取当前用户上下文的数据隔离编号
    /// </summary>
    /// <param name="userContext"></param>
    /// <param name="dataIsolateLevel"></param>
    public static string? GetDataIsolateId(this UserContext userContext, DataIsolateLevel dataIsolateLevel)
    {
        if (dataIsolateLevel != DataIsolateLevel.None && !userContext.IsAuthorizer)
        {
            switch (dataIsolateLevel)
            {
                case DataIsolateLevel.Individual:
                    return userContext.UserId;
                case DataIsolateLevel.Subordinate:
                    if (!string.IsNullOrWhiteSpace(userContext.User.GroupId))
                    {
                        return userContext.User.GroupId;
                    }
                    break;
                case DataIsolateLevel.TopGroup:
                    if (!string.IsNullOrWhiteSpace(userContext.User.GroupId))
                    {
                        var topGroup = userContext.Groups.GetTopParent<GroupViewModel>(userContext.User.GroupId);
                        if (topGroup is not null)
                        {
                            return topGroup.Id;
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        return null;
    }

    /// <summary>
    /// 初始化用户 Claims
    /// </summary>
    /// <param name="context"></param>
    /// <param name="user"></param>
    /// <returns></returns>
    public static async Task InitUserClaimsAsync(this IdentityDbContext context, User user)
    {
        var claims = new List<Claim>();
        var userClaims = await context.UserClaims.AsNoTracking().Where(x => x.UserId == user.Id).ToListAsync();
        var userId = user.Id.ToString()!;
        if (!userClaims.Any(x => x.ClaimType == CLAIM_TYPE_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_ID, userId));
        }

        if (!userClaims.Any(x => x.ClaimType == CLAIM_TYPE_USER_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_ID, userId));
        }

        if (user.UserName is not null && !userClaims.Any(x => x.ClaimType == CLAIM_TYPE_USER_NAME))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_NAME, user.UserName));
        }

        if (user.Name is not null && !userClaims.Any(x => x.ClaimType == CLAIM_TYPE_USER_NAME_IDENTIFIER))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_NAME_IDENTIFIER, user.Name));
        }

        if (claims.Count != 0)
        {
            var last = await context.UserClaims.OrderBy(x => x.Id).Select(x => x.Id).LastOrDefaultAsync();
            var index = last + 1;
            foreach (var claim in claims)
            {
                await context.AddAsync(new UserClaim { Id = index++, ClaimType = claim.Type, ClaimValue = claim.Value, UserId = user.Id });
            }

            await context.SaveChangesAsync();
        }
    }
    /// <summary>
    /// 初始化用户 Claims
    /// </summary>
    /// <param name="userManager"></param>
    /// <param name="user"></param>
    /// <returns></returns>
    public static async Task<IdentityResult> InitUserClaimsAsync(this UserManager<User> userManager, User user)
    {
        var claims = new List<Claim>();
        var userClaims = await userManager.GetClaimsAsync(user);
        var userId = user.Id.ToString()!;
        if (!userClaims.Any(x => x.Type == CLAIM_TYPE_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_ID, userId));
        }

        if (!userClaims.Any(x => x.Type == CLAIM_TYPE_USER_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_ID, userId));
        }

        if (user.UserName is not null && !userClaims.Any(x => x.Type == CLAIM_TYPE_USER_NAME))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_NAME, user.UserName));
        }

        if (user.Name is not null && !userClaims.Any(x => x.Type == CLAIM_TYPE_USER_NAME_IDENTIFIER))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_NAME_IDENTIFIER, user.Name));
        }

        if (claims.Count != 0)
        {
            return await userManager.AddClaimsAsync(user, claims);
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 根据用户上下文获取自定义 claims
    /// </summary>
    /// <param name="user"></param>
    /// <param name="context"></param>
    /// <param name="dataIsolateLevel"></param>
    /// <returns></returns>
    public static async Task<List<Claim>> GetUserClaimsAsync(this UserViewModel user, IdentityDbContext context, DataIsolateLevel dataIsolateLevel)
    {
        ArgumentNullException.ThrowIfNull(user.Name);
        ArgumentNullException.ThrowIfNull(user.UserName);

        var claims = new List<Claim>{
            new(JwtClaimTypes.Name, user.Name),
            new(CLAIM_TYPE_USER_ID, user.Id),
            new(CLAIM_TYPE_USER_NAME, user.UserName),
            new(CLAIM_TYPE_USER_STATE, user.State.ToString()),
            new(ClaimTypes.Role, GROUP_AUTHENTICATOR)
        };

        if (!string.IsNullOrWhiteSpace(user.Alias))
        {
            claims.Add(new(CLAIM_TYPE_ALIAS, user.Alias));
        }

        if (!string.IsNullOrWhiteSpace(user.Image))
        {
            claims.Add(new(CLAIM_TYPE_IMAGE, user.Image));
        }

        if (!string.IsNullOrWhiteSpace(user.GroupId))
        {
            claims.Add(new(CLAIM_TYPE_GROUP_ID, user.GroupId));
        }

        //if (user.HasRealNameAuthentication is not null)
        //{
        //    claims.Add(new(CLAIM_TYPE_HAS_REAL_NAME_AUTH, user.HasRealNameAuthentication.Value.ToString()));
        //}

        if (dataIsolateLevel != DataIsolateLevel.None && user.Type < AuthorizeType.Authorizer)
        {
            switch (dataIsolateLevel)
            {
                case DataIsolateLevel.Individual:
                    claims.Add(new Claim(CLAIM_TYPE_DATA_ISOLATE_ID, user.Id));
                    break;
                case DataIsolateLevel.Subordinate:
                    if (!string.IsNullOrWhiteSpace(user.GroupId))
                    {
                        claims.Add(new Claim(CLAIM_TYPE_DATA_ISOLATE_ID, user.GroupId));
                    }
                    break;
                case DataIsolateLevel.TopGroup:
                    if (!string.IsNullOrWhiteSpace(user.GroupId))
                    {
                        var topGroup = await context.GetTopParentAsync<IdentityDbContext, Group>(user.GroupId);
                        if (topGroup is not null)
                        {
                            claims.Add(new Claim(CLAIM_TYPE_DATA_ISOLATE_ID, topGroup.Id));
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        return claims;
    }

    /// <summary>
    /// 查找组织单位的全部用户
    /// </summary>
    /// <param name="context">数据库上下文</param>
    /// <param name="groupIds">组织单位</param>
    /// <returns></returns>
    public static async Task<List<User>?> GetGroupUsersAsync(this IdentityDbContext context, params string[] groupIds)
    {
        var gids = new List<string>(groupIds);
        foreach (var groupId in groupIds)
        {
            var childGroups = await context.GetChildrenAsync<IdentityDbContext, Group>(groupId, true);
            if (childGroups.Count > 0)
            {
                groupIds.AddRange(childGroups.Select(x => x.Id));
            }
        }

        var userQueryable = context.Users.Where(x => x.State == DataState.Available && x.UserName != null);
        var userIds = await context.UserGroups.Where(x => groupIds.Contains(x.GroupId)).Select(x => x.UserId).Distinct().ToListAsync();
        if (userIds.Count > 0)
        {
            return await userQueryable.Where(x => userIds.Contains(x.Id) || x.GroupId != null && groupIds.Contains(x.GroupId)).ToListAsync();
        }

        return default;
    }
}